package android.sdk;

import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OptionalDataException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;

import android.dto.AccelerometerRate;
import android.dto.DataTransferObject;
import android.dto.DeviceConnect;
import android.dto.ViewEvent;
import android.os.Build;
import android.util.Log;

public class NetworkController implements Runnable {
	
	private volatile Socket socket;
	private volatile ObjectOutputStream output;
	private volatile ObjectInputStream input;
	private SDKClientService service;
	
	private Thread currentThread;
	private boolean streamClosing = false;
	private boolean streamCanClose = false;
	
	private String address;
	private int port;
	private int timeout;
	
	public NetworkController(SDKClientService sdkClientService) {
		this.service = sdkClientService; 
	}

	public synchronized void close(){
		Log.i("Tesis", "Close() " + Thread.currentThread().getName()); /////////////
		this.service.disconnect();
		
		if (output != null && input != null && socket != null) {
			try {
				output.flush();
				output.close();
				input.close();
				socket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	public synchronized void sendObject(DataTransferObject dto) {
		Log.i("Tesis", "sendObject() " + Thread.currentThread().getName()); /////////////
		try {
			if (!streamClosing && socket != null && output != null && socket.isConnected() && !socket.isClosed()){
				Log.i("Tesis", "sendObject() --> Enviando " + Thread.currentThread().getName()); /////////////
				output.writeObject(dto);
			} else
				streamCanClose = true;
		} catch (IOException e) {
			Log.e("Tesis", "IOException! " + e.getMessage()); //////////
			streamCanClose = true;
		}
	}
	
	public synchronized void connect(String address, int port, int timeout) {
		this.address = address;
		this.port = port;
		this.timeout = timeout;
		
		Log.i("Tesis", "1 - Connect() " + Thread.currentThread().getName()); /////////////
		if(this.socket == null || this.socket.isClosed()){
			Thread thread = new Thread(this);
			currentThread = thread;
			Log.i("Tesis", "Arrancando thread " + thread.getName()); /////////////
			thread.start();
		}
	}
	
	public void disconnect() {
		Log.i("Tesis", "Disconnect " + Thread.currentThread().getName());
		DataTransferObject dto = new DataTransferObject();
		dto.setDeviceId(this.service.getDeviceId());
		dto.setType(DataTransferObject.DEVICE_DISCONNECT);
		sendObject(dto);
		
		streamClosing = true;
	}


	@Override
	public void run() {
		if((this.socket == null || this.socket.isClosed()) && (Thread.currentThread() == currentThread)){
			synchronized (this) {
				try {
					Log.i("Tesis", "Abriendo socket y streams " + Thread.currentThread().getName()); /////////////
					//this.socket = new Socket(this.address, this.port);
					Log.i("Tesis", "Timeout: " + this.timeout);
					this.socket = new Socket();
					this.socket.connect(new InetSocketAddress(this.address, this.port), this.timeout);
					this.output = new ObjectOutputStream(this.socket.getOutputStream());
					this.output.flush();
					this.input = new ObjectInputStream(this.socket.getInputStream());
					streamClosing = false;
					streamCanClose = false;
				
					DeviceConnect deviceConnect = new DeviceConnect();
					deviceConnect.setDeviceId(this.service.getDeviceId());
					deviceConnect.setType(DataTransferObject.DEVICE_CONNECT);
					deviceConnect.setBrand(Build.BRAND);
					deviceConnect.setDevice(Build.DEVICE);
					deviceConnect.setDisplay(Build.DISPLAY);
					deviceConnect.setManufacturer(Build.MANUFACTURER);
					deviceConnect.setModel(Build.MODEL);
					deviceConnect.setProduct(Build.PRODUCT);
					sendObject(deviceConnect);
					
					service.connectionEstablished();
				} catch (SocketTimeoutException e) {
					Log.i("Tesis", "Connection timed out");
					this.service.connectionFailed(e.getMessage());
					socketClose();
					return;
				} catch (ConnectException e) {
					Log.i("Tesis", "Connection refused");
					this.service.connectionFailed(e.getMessage());
					socketClose();
					return;
				} catch (UnknownHostException e) {
					Log.i("Tesis", "Unknown host");
					this.service.connectionFailed(e.getMessage());
					socketClose();
					return;				
				} catch (IOException e) {
					Log.i("Tesis", "IOException");
					this.service.connectionFailed(e.getMessage());
					socketClose();
					return;
				}				
			} 
			
			try {
				Log.i("Tesis", "Leyendo input 1 " + Thread.currentThread().getName()); /////////////
				Log.d("Tesis", "input 1 " + input.toString()); /////////////
				DataTransferObject dto = (DataTransferObject)this.input.readObject();
				while (true) {
					this.processObject(dto);
					Log.i("Tesis", "Leyendo input 2 " + Thread.currentThread().getName()); /////////////
					if (!streamClosing && socket.isConnected() && !socket.isClosed()){
						Log.d("Tesis", "input 2 " + input.toString()); /////////////
						dto = (DataTransferObject)this.input.readObject();
					}
					else {
						Log.i("Tesis", "No pudo leer, socket cerrado " + Thread.currentThread().getName());
						streamCanClose = true;
						break;
					}	
				}
			} catch (SocketException e) {
				Log.i("Tesis", "Socket Exception");
				streamClosing = true;
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			} catch (ClassCastException e) { 
				e.printStackTrace();
			} catch (EOFException e) {
				Log.i("Tesis", "Connection reset by server");
				streamCanClose = true;
			} catch (OptionalDataException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				Log.i("Tesis", "Finally! streamClosing = " + streamClosing + ", streamCanClose = " + streamCanClose);
				if (streamClosing) {
					Log.i("Tesis", "IF del finally");
					while (!streamCanClose) {}
					close();
				}
			}
		}
	}
	
	private void processObject(DataTransferObject dto) {
		Log.i("Tesis", "Procesando DTO " + Thread.currentThread().getName()); /////////////
		switch (dto.getType()) {
			case DataTransferObject.DEVICE_DISCONNECT:
				streamClosing = true;
				break;
		
			case DataTransferObject.ACCELEROMETER_REGISTER_EVENT:
				this.service.registerAccelerometerEvent();
				break;	
				
			case DataTransferObject.ACCELEROMETER_UNREGISTER_EVENT:
				this.service.unregisterAccelerometerEvent();
				break;	

			case DataTransferObject.ACCELEROMETER_RATE_SET:
				AccelerometerRate rate = (AccelerometerRate) dto;
				this.service.setAccelerometerRate(rate.getRate());
				break;	

			case DataTransferObject.TOUCHSCREEN_REGISTER_CLICK_EVENT:
				ViewEvent viewEventClickR = (ViewEvent) dto;
				this.service.registerOnClickEvent(viewEventClickR.getId());
				break;	
				
			case DataTransferObject.TOUCHSCREEN_UNREGISTER_CLICK_EVENT:
				ViewEvent viewEventClickU = (ViewEvent) dto;
				this.service.unregisterOnClickEvent(viewEventClickU.getId());
				break;	
				
			case DataTransferObject.TOUCHSCREEN_REGISTER_LONGCLICK_EVENT:
				ViewEvent viewEventLongClickR = (ViewEvent) dto;
				this.service.registerOnLongClickEvent(viewEventLongClickR.getId());
				break;	
				
			case DataTransferObject.TOUCHSCREEN_UNREGISTER_LONGCLICK_EVENT:
				ViewEvent viewEventLongClickU = (ViewEvent) dto;
				this.service.unregisterOnLongClickEvent(viewEventLongClickU.getId());
				break;	
				
			case DataTransferObject.TOUCHSCREEN_REGISTER_TOUCH_EVENT:
				ViewEvent viewEventTouchR = (ViewEvent) dto;
				this.service.registerOnTouchEvent(viewEventTouchR.getId());
				break;	
				
			case DataTransferObject.TOUCHSCREEN_UNREGISTER_TOUCH_EVENT:
				ViewEvent viewEventTouchU = (ViewEvent) dto;
				this.service.unregisterOnTouchEvent(viewEventTouchU.getId());
				break;	
				
		}
		
	}
	
	private void socketClose() {
		try {
			socket.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}
